<?php
/*!
	\file class.SimpleDBO.php
	\brief Contains the class definition for the SimpleDBO object
*/

// These constants are not used in this class, but are defined here because several of the helper classes use them.
if(!defined(SIMPLEDBO_CLASSNAME_PREFIX))
{
	define(SIMPLEDBO_CLASSNAME_PREFIX,     "DBO_"   ); //!< First part of the subclasses that extend the SimpleDBO base class.
	define(SIMPLEDBO_CLASSFILENAME_PREFIX, "class." ); //!< First part of the filename that the SimpleDBO subclasses are saved in (the second part is the class' name itself)
	define(SIMPLEDBO_CLASSFILENAME_SUFFIX, ".php"   ); //!< File extension of the files that the SimpleDBO subclasses are saved in
	define(SIMPLEDBO_LIB_BASEPATH,         "lib/SimpleDBO/" ); //!< File path where the SimpleDBO base and SQL classes are kept
	define(SIMPLEDBO_LIB_SUBPATH,          SIMPLEDBO_LIB_BASEPATH . "dbo/" ); //!< File path where the SimpleDBO subclasses are stored.
}

/*!
	\class SimpleDBO
	\author Alex McClung
	\date Created: January 30, 2006
	\date Modified: November 6, 2007
	\brief SimpleDBO is a database object that encapsulates all the functionality a row in a table might have.
	
	July 30, 2006: Added compatibility support for PHP version 4 and under.
	November 6, 2007: Escaped EVERYTHING going into a query.
*/
abstract class SimpleDBO
{
	protected $_dbo_sql;	     //!< SQL instance
	protected $_dbo_table_name;  //!< Name of the table this class will use
	protected $_dbo_id_col;		 //!< Primary key column name
	
	/*!
		\brief Class constructor
		\param $id - (optional) primary key value for a single row in a table, when passed in, values from this row will be loaded into the object's properties
		\returns SimpleDBO class instance; if the id parameter is given and does not match a row in the table, the constructor will return false
	*/
	function __construct($id=NULL)
	{
		// Use a global variable to store the SQL connection so we don't have
		// to reconnect every time the class is instantiated.
		if(!isset($GLOBALS['_dbo_sql']))
			$this->Reconnect();
		else
			$this->_dbo_sql = $GLOBALS['_dbo_sql'];
		
		// Did the user pass in an ID? Try and fetch a row from the table
		if($id != null)
			if(!$this->Get($id))
				return false;
	}
	
	/*!
		\brief (Re)connects to the database. Handy if you want to preserve SimpleDBO objects in $_SESSION
		\returns nothing
	*/
	public function Reconnect()
	{
		$this->_dbo_sql = new SQL();
		$GLOBALS['_dbo_sql'] = $this->_dbo_sql;
	}

	/*!
		\brief Loads column values into the object's properties from a row in the object's table
		\param $id - primary key value for a row in the table
		\returns true if successful, false on failure
	*/
	public function Get($id)
	{
		$query = "SELECT * FROM `" . $this->_dbo_sql->Escape($this->_dbo_table_name) . "` WHERE {$this->_dbo_id_col}='" . $this->_dbo_sql->Escape($id) . "';";
		if($this->_dbo_sql->Query($query))
		{
			$row = $this->_dbo_sql->GetObject();
			$columns = $this->GetColumnNames();
			foreach($columns as $column)
				$this->$column = stripslashes($row->$column);
			
			return true;
		}
		
		return false;
	}
	
	/*!
		\brief Inserts a row in the table
		\returns MySQL result identifier if successful, false on failure
	*/
	public function Insert()
	{
		$columns  = $this->GetColumnNames();
		$nColumns = count($columns);
		$values   = array();
		
		for($i=0; $i<$nColumns; $i++)
		{
			$column = $columns[$i];
			$values[$i]  = $this->_dbo_sql->Escape($this->$column);
			$columns[$i] = $this->_dbo_sql->Escape($column);
		}
		
		$query = "INSERT INTO `" . $this->_dbo_sql->Escape($this->_dbo_table_name) . "` (`" . implode($columns, "`, `") . "`) VALUES ('" . implode($values, "', '") . "');";
		
		// Keep the result, because there is one last thing to do
		$result = $this->_dbo_sql->Query($query);
		
		if(!$result)
			return false;
			
		// Set this object's id property to the newly inserted id (IF it is an AUTO INCREMENT int)
		if($this->_dbo_sql->LastID($result) > 0) // This will be greater than zero if this table uses an AUTO INCREMENT field as its primary key
			$this->{$this->_dbo_id_col} = $this->_dbo_sql->LastID($result);
		
		return $result;
	}
	
	/*!
		\brief Updates the row data in the table; the primary key value must be set.
		\returns MySQL result identifier if successful, false on failure
	*/
	public function Update()
	{
		$columns    = $this->GetColumnNames();
		$conditions = array();
		$idval      = "";
		
		foreach($columns as $column)
			if($column != $this->_dbo_id_col)
				$conditions[] = "`" . $this->_dbo_sql->Escape($column) . "`='" . $this->_dbo_sql->Escape($this->$column) . "'";
			else
				$idval = $this->_dbo_sql->Escape($this->$column);
				
		$query = "UPDATE `{$this->_dbo_table_name}` SET " . implode($conditions, ", ") . " WHERE {$this->_dbo_id_col}='" . $idval . "';";
		
		return $this->_dbo_sql->Query($query);
	}
	
	/*!
		\brief Removes a row from the table; the primary key value must be set.
		\returns MySQL result identifier if successful, false on failure
	*/
	public function Remove()
	{
		$query = "DELETE FROM `" . $this->_dbo_sql->Escape($this->_dbo_table_name) . "` WHERE " . $this->_dbo_sql->Escape($this->_dbo_id_col) . "='" . $this->_dbo_sql->Escape($this->{$this->_dbo_id_col}) . "';";
		return $this->_dbo_sql->Query($query);
	}
	
	/*!
		\brief Gets the column names (or class properties) in the table
		\returns string array of column names
	*/
	public function GetColumnNames()
	{
		// Does the global variable for this table exist?
		if(isset($GLOBALS['SimpleDBO_Columns'][$this->_dbo_table_name]))
		{
			// Return the cached value, so that we don't have to do everything below.
			return $GLOBALS['SimpleDBO_Columns'][$this->_dbo_table_name];
		}
		
		// Now we have to look up the column names somehow. First, though, we have
		// to figure out which version of PHP we're running.
		list($version, $null) = split("\.", phpversion());
		
		// If we use PHP5, reflection is the clear winner
		if(intval($version) >= 5)
		{
			$obj        = new ReflectionClass($this);
			$properties = $obj->getProperties();
			$columns    = array();
			
			// All of this class' public properties (anything that does not
			// begin with '_dbo_') are the column names
			foreach($properties as $property)
			{
				$propertyname = $property->getName();
				if($property->isPublic() && substr($propertyname,0,5) != "_dbo_")
					$columns[] = $propertyname;
			}
		}
		// We're not running PHP 5, so we have to query the database
		else
		{
			// Query the DB and ge the columns in this table
			$columns = $this->_dbo_sql->Columns($this->_dbo_table_name);
		}
		
		// We should now have our list of clolumn names
		// Stick them in the global variables in case this function is called again
		$GLOBALS['SimpleDBO_Columns'][$this->_dbo_table_name] = $columns;
		
		return $columns;
	}
	
	/*!
		\brief Retrieves the last MySQL error.
		\returns String
	*/
	public function LastError()
	{
		return $this->_dbo_sql->LastError();
	}
	
	/*!
		\brief Custom get function; provides read-only access to certain _dbo_* properties
		\returns mixed
	*/
	public function __get($varname)
	{
		if($varname == "_dbo_id_column")
			return $this->_dbo_id_col;
		if($varname == "_dbo_table")
			return $this->_dbo_table_name;
		
		return $this->$varname;
	}
}
?>